home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / tabCache.js < prev    next >
Text File  |  2010-01-15  |  17KB  |  521 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10.  
  11. const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
  12. const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2);
  13. const versionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
  14. const appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
  15.  
  16. // Maximum cached size of a signle response (bytes)
  17. var responseSizeLimit = 1024 * 1024 * 5;
  18.  
  19. // List of text content types. These content-types are cached.
  20. var contentTypes =
  21. {
  22.     "text/plain": 1,
  23.     "text/html": 1,
  24.     "text/xml": 1,
  25.     "text/xsl": 1,
  26.     "text/xul": 1,
  27.     "text/css": 1,
  28.     "text/sgml": 1,
  29.     "text/rtf": 1,
  30.     "text/x-setext": 1,
  31.     "text/richtext": 1,
  32.     "text/javascript": 1,
  33.     "text/jscript": 1,
  34.     "text/tab-separated-values": 1,
  35.     "text/rdf": 1,
  36.     "text/xif": 1,
  37.     "text/ecmascript": 1,
  38.     "text/vnd.curl": 1,
  39.     "text/x-json": 1,
  40.     "text/x-js": 1,
  41.     "text/js": 1,
  42.     "text/vbscript": 1,
  43.     "view-source": 1,
  44.     "view-fragment": 1,
  45.     "application/xml": 1,
  46.     "application/xhtml+xml": 1,
  47.     "application/vnd.mozilla.xul+xml": 1,
  48.     "application/javascript": 1,
  49.     "application/x-javascript": 1,
  50.     "application/x-httpd-php": 1,
  51.     "application/rdf+xml": 1,
  52.     "application/ecmascript": 1,
  53.     "application/http-index-format": 1,
  54.     "application/json": 1,
  55.     "application/x-js": 1,
  56.     "multipart/mixed" : 1,
  57.     "multipart/x-mixed-replace" : 1
  58. };
  59.  
  60. // ************************************************************************************************
  61. // Model implementation
  62.  
  63. /**
  64.  * Implementation of cache model. The only purpose of this object is to register an HTTP
  65.  * observer so, HTTP communication can be interecepted and all incoming data stored within
  66.  * a cache.
  67.  */
  68. Firebug.TabCacheModel = extend(Firebug.Module,
  69. {
  70.     dispatchName: "tabCache",
  71.     contentTypes: contentTypes,
  72.     fbListeners: [],
  73.  
  74.     initializeUI: function(owner)
  75.     {
  76.         responseSizeLimit = Firebug.getPref(Firebug.prefDomain, "cache.responseLimit");
  77.  
  78.         // Read additional text mime-types from preferences.
  79.         var mimeTypes = Firebug.getPref(Firebug.prefDomain, "cache.mimeTypes");
  80.         if (mimeTypes)
  81.         {
  82.             var list = mimeTypes.split(" ");
  83.             for (var i=0; i<list.length; i++)
  84.                 contentTypes[list[i]] = 1;
  85.         }
  86.  
  87.         // Register for HTTP events.
  88.         if (Ci.nsITraceableChannel)
  89.             httpObserver.addObserver(this, "firebug-http-event", false);
  90.     },
  91.  
  92.     shutdown: function()
  93.     {
  94.         if (Ci.nsITraceableChannel)
  95.             httpObserver.removeObserver(this, "firebug-http-event");
  96.     },
  97.  
  98.     initContext: function(context)
  99.     {
  100.     },
  101.  
  102.     /* nsIObserver */
  103.     observe: function(subject, topic, data)
  104.     {
  105.         try
  106.         {
  107.             if (!(subject instanceof Ci.nsIHttpChannel))
  108.                 return;
  109. // XXXjjb this same code is in net.js, better to have it only once
  110.             var win = getWindowForRequest(subject);
  111.             if (win)
  112.                 var tabId = Firebug.getTabIdForWindow(win);
  113.             if (!tabId)
  114.                 return;
  115.  
  116.             if (topic == "http-on-modify-request")
  117.                 this.onModifyRequest(subject, win, tabId);
  118.             else if (topic == "http-on-examine-response")
  119.                 this.onExamineResponse(subject, win, tabId);
  120.             else if (topic == "http-on-examine-cached-response")
  121.                 this.onCachedResponse(subject, win, tabId);
  122.         }
  123.         catch (err)
  124.         {
  125.         }
  126.     },
  127.  
  128.     onModifyRequest: function(request, win, tabId)
  129.     {
  130.     },
  131.  
  132.     onExamineResponse: function(request, win, tabId)
  133.     {
  134.         this.registerStreamListener(request, win);
  135.     },
  136.  
  137.     onCachedResponse: function(request, win, tabId)
  138.     {
  139.         this.registerStreamListener(request, win);
  140.     },
  141.  
  142.     registerStreamListener: function(request, win)
  143.     {
  144.         try
  145.         {
  146.             // Due to #489317, the content type must be checked in onStartRequest
  147.             //if (!this.shouldCacheRequest(request))
  148.             //    return;
  149.  
  150.             request.QueryInterface(Ci.nsITraceableChannel);
  151.  
  152.             // Create Firebug's stream listener that is tracing HTTP responses.
  153.             var newListener = CCIN("@joehewitt.com/firebug-channel-listener;1", "nsIStreamListener");
  154.             newListener.wrappedJSObject.window = win;
  155.  
  156.             // Set proxy listener for back notifiction from XPCOM to chrome (using real interface
  157.             // so nsIRequest object is properly passed from XPCOM scope).
  158.             newListener.QueryInterface(Ci.nsITraceableChannel);
  159.             newListener.setNewListener(new ChannelListenerProxy(win));
  160.  
  161.             // Add tee listener into the chain of request stream listeners so, the chain
  162.             // doesn't include a JS code. This way all exceptions are propertly distributed
  163.             // (#515051).
  164.             // The nsIStreamListenerTee differes in following branches:
  165.             // 1.9.1: not possible to registere nsIRequestObserver with tee
  166.             // 1.9.2: implements nsIStreamListenerTee_1_9_2 with initWithObserver methods
  167.             // 1.9.3: adds third parameter to the existing init method.
  168.             if (versionChecker.compare(appInfo.version, "3.6*") >= 0)
  169.             {
  170.                 var tee = CCIN("@mozilla.org/network/stream-listener-tee;1", "nsIStreamListenerTee");
  171.                 tee = tee.QueryInterface(Ci.nsIStreamListenerTee);
  172.  
  173.                 if (Ci.nsIStreamListenerTee_1_9_2)
  174.                     tee = tee.QueryInterface(Ci.nsIStreamListenerTee_1_9_2);
  175.  
  176.                 // The response will be written into the outputStream of this pipe.
  177.                 // Both ends of the pipe must be blocking.
  178.                 var sink = CCIN("@mozilla.org/pipe;1", "nsIPipe");
  179.                 sink.init(true, true, 0, 0, null);
  180.  
  181.                 var originalListener = request.setNewListener(tee);
  182.                 newListener.wrappedJSObject.sink = sink;
  183.  
  184.                 if (tee.initWithObserver)
  185.                     tee.initWithObserver(originalListener, sink.outputStream, newListener);
  186.                 else
  187.                     tee.init(originalListener, sink.outputStream, newListener);
  188.             }
  189.             else
  190.             {
  191.                 newListener.wrappedJSObject.listener = request.setNewListener(newListener);
  192.             }
  193.  
  194.             // xxxHonza: this is a workaround for #489317. Just passing
  195.             // shouldCacheRequest method to the component so, onStartRequest
  196.             // can decide whether to cache or not.
  197.             newListener.wrappedJSObject.shouldCacheRequest = function(request)
  198.             {
  199.                 try {
  200.                     return Firebug.TabCacheModel.shouldCacheRequest(request)
  201.                 } catch (err) {}
  202.                 return false;
  203.             }
  204.  
  205.             // xxxHonza: this is a workaround for the tracing-listener to get the
  206.             // right context. Notice that if the window (parent browser) is closed
  207.             // during the response download the TabWatcher (used within this method)
  208.             // is undefined. But in such a case no cache is needed anyway.
  209.             // Another thing is that the context isn't available now, but will be
  210.             // as soon as this method is used from the stream listener.
  211.             newListener.wrappedJSObject.getContext = function(win)
  212.             {
  213.                 try {
  214.                     return TabWatcher.getContextByWindow(win);
  215.                 } catch (err){}
  216.                 return null;
  217.             }
  218.         }
  219.         catch (err)
  220.         {
  221.         }
  222.     },
  223.  
  224.     shouldCacheRequest: function(request)
  225.     {
  226.         request.QueryInterface(Ci.nsIHttpChannel);
  227.  
  228.         // Allow to customize caching rules.
  229.         if (dispatch2(this.fbListeners, "shouldCacheRequest", [request]))
  230.             return true;
  231.  
  232.         // Cache only text responses for now.
  233.         var contentType = request.contentType;
  234.         if (contentType)
  235.             contentType = contentType.split(";")[0];
  236.  
  237.         contentType = trim(contentType);
  238.         if (contentTypes[contentType])
  239.             return true;
  240.  
  241.         // Hack to work around application/octet-stream for js files (2063).
  242.         // Let's cache all files with js extensions.
  243.         var extension = getFileExtension(safeGetName(request));
  244.         if (extension == "js")
  245.             return true;
  246.  
  247.         return false;
  248.     },
  249. });
  250.  
  251. // ************************************************************************************************
  252.  
  253. /**
  254.  * This cache object is intended to cache all responses made by a specific tab.
  255.  * The implementation is based on nsITraceableChannel interface introduced in
  256.  * Firefox 3.0.4. This interface allows to intercept all incoming HTTP data.
  257.  *
  258.  * This object replaces the SourceCache, which still exist only for backward
  259.  * compatibility.
  260.  *
  261.  * The object is derived from SourceCache so, the same interface and most of the
  262.  * implementation is used.
  263.  */
  264. Firebug.TabCache = function(context)
  265. {
  266.     Firebug.SourceCache.call(this, context);
  267. };
  268.  
  269. Firebug.TabCache.prototype = extend(Firebug.SourceCache.prototype,
  270. {
  271.     responses: [],       // responses in progress.
  272.  
  273.     storePartialResponse: function(request, responseText, win)
  274.     {
  275.         try
  276.         {
  277.             responseText = FBL.convertToUnicode(responseText, win.document.characterSet);
  278.         }
  279.         catch (err)
  280.         {
  281.         }
  282.  
  283.         var url = safeGetName(request);
  284.         var response = this.getResponse(request);
  285.  
  286.         // Size of each response is limited.
  287.         var limitNotReached = true;
  288.         if (response.size + responseText.length >= responseSizeLimit)
  289.         {
  290.             limitNotReached = false;
  291.             responseText = responseText.substr(0, responseSizeLimit - response.size);
  292.             FBTrace.sysout("tabCache.storePartialResponse Max size limit reached for: " + url);
  293.         }
  294.  
  295.         response.size += responseText.length;
  296.  
  297.         // Store partial content into the cache.
  298.         this.store(url, responseText);
  299.  
  300.         // Return false if furhter parts of this response should be ignored.
  301.         return limitNotReached;
  302.     },
  303.  
  304.     getResponse: function(request)
  305.     {
  306.         var url = safeGetName(request);
  307.         var response = this.responses[url];
  308.         if (!response)
  309.         {
  310.             this.invalidate(url);
  311.             this.responses[url] = response = {
  312.                 request: request,
  313.                 size: 0
  314.             };
  315.         }
  316.  
  317.         return response;
  318.     },
  319.  
  320.     storeSplitLines: function(url, lines)
  321.     {
  322.         var currLines = this.cache[url];
  323.         if (!currLines)
  324.             currLines = this.cache[url] = [];
  325.  
  326.         // Join the last line with the new first one so, the source code
  327.         // lines are properly formatted...
  328.         if (currLines.length)
  329.         {
  330.             // ... but only if the last line isn't already completed.
  331.             var lastLine = currLines[currLines.length-1];
  332.             if (lastLine && lastLine.search(/\r|\n/) == -1)
  333.                 currLines[currLines.length-1] += lines.shift();
  334.         }
  335.  
  336.         // Append new lines (if any) into the array for specified url.
  337.         if (lines.length)
  338.             this.cache[url] = currLines.concat(lines);
  339.  
  340.         return this.cache[url];
  341.     },
  342.  
  343.     loadFromCache: function(url, method, file)
  344.     {
  345.         // The ancestor implementation (SourceCache) uses ioService.newChannel, which
  346.         // can result in additional request to the server (in case the response can't
  347.         // be loaded from the Firefox cache) - known as double-load problem.
  348.         // This new implementation (TabCache) uses nsITraceableChannel so, all responses
  349.         // should be already cached.
  350.  
  351.         // xxxHonza: TODO entire implementation of this method should be removed in Firebug 1.5
  352.         // xxxHonza: let's try to get the response from the cache till #449198 is fixed.
  353.         var stream;
  354.         var responseText;
  355.         try
  356.         {
  357.             if (!url)
  358.                 return responseText;
  359.  
  360.             var channel = ioService.newChannel(url, null, null);
  361.  
  362.             // These flag combination doesn't repost the request.
  363.             channel.loadFlags = Ci.nsIRequest.LOAD_FROM_CACHE |
  364.                 Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
  365.                 Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
  366.  
  367.             var charset = "UTF-8";
  368.             var doc = this.context.window.document;
  369.             if (doc)
  370.                 charset = doc.characterSet;
  371.  
  372.             stream = channel.open();
  373.  
  374.             // The response doesn't have to be in the browser cache.
  375.             if (!stream.available())
  376.             {
  377.                 stream.close();
  378.                 return [$STR("message.Failed to load source for") + ": " + url];
  379.             }
  380.  
  381.             // Don't load responses that shouldn't be cached.
  382.             if (!Firebug.TabCacheModel.shouldCacheRequest(channel))
  383.             {
  384.                 stream.close();
  385.                 return [$STR("message.The resource from this URL is not text") + ": " + url];
  386.             }
  387.  
  388.             responseText = readFromStream(stream, charset);
  389.  
  390.             responseText = this.store(url, responseText);
  391.         }
  392.         catch (err)
  393.         {
  394.         }
  395.         finally
  396.         {
  397.             if (stream)
  398.                 stream.close();
  399.         }
  400.  
  401.         return responseText;
  402.     },
  403.  
  404.     // nsIStreamListener - callbacks from channel stream listener component.
  405.     onStartRequest: function(request, requestContext)
  406.     {
  407.         var response = this.getResponse(request);
  408.  
  409.         dispatch(Firebug.TabCacheModel.fbListeners, "onStartRequest", [this.context, request]);
  410.         dispatch(this.fbListeners, "onStartRequest", [this.context, request]);
  411.     },
  412.  
  413.     onDataAvailable: function(request, requestContext, inputStream, offset, count)
  414.     {
  415.         var stream = {
  416.             value: inputStream
  417.         };
  418.  
  419.         dispatch(Firebug.TabCacheModel.fbListeners, "onDataAvailable",
  420.             [this.context, request, requestContext, stream, offset, count]);
  421.         dispatch(this.fbListeners, "onDataAvailable", [this.context,
  422.             request, requestContext, stream, offset, count]);
  423.  
  424.         return stream.value;
  425.     },
  426.  
  427.     onStopRequest: function(request, requestContext, statusCode)
  428.     {
  429.         // The response is finally received so, remove the request from the list of
  430.         // current responses.
  431.         var url = safeGetName(request);
  432.         delete this.responses[url];
  433.  
  434.         var lines = this.cache[this.removeAnchor(url)];
  435.         var responseText = lines ? lines.join("") : "";
  436.  
  437.         dispatch(Firebug.TabCacheModel.fbListeners, "onStopRequest", [this.context, request, responseText]);
  438.         dispatch(this.fbListeners, "onStopRequest", [this.context, request, responseText]);
  439.     }
  440. });
  441.  
  442. // ************************************************************************************************
  443. // Proxy Listener
  444.  
  445. function ChannelListenerProxy(win)
  446. {
  447.     this.wrappedJSObject = this;
  448.     this.window = win;
  449. }
  450.  
  451. ChannelListenerProxy.prototype =
  452. {
  453.     /* nsIStreamListener */
  454.     onStartRequest: function(request, requestContext)
  455.     {
  456.         var context = this.getContext();
  457.         if (context)
  458.             context.sourceCache.onStartRequest(request, requestContext);
  459.     },
  460.  
  461.     onDataAvailable: function(request, requestContext, inputStream, offset, count)
  462.     {
  463.         var context = this.getContext();
  464.         if (!context)
  465.             return null;
  466.  
  467.         return context.sourceCache.onDataAvailable(request, requestContext, inputStream, offset, count);
  468.     },
  469.  
  470.     onStopRequest: function(request, requestContext, statusCode)
  471.     {
  472.         var context = this.getContext();
  473.         if (context)
  474.             context.sourceCache.onStopRequest(request, requestContext, statusCode);
  475.     },
  476.  
  477.     /* nsISupports */
  478.     QueryInterface: function(iid)
  479.     {
  480.         if (iid.equals(Ci.nsIStreamListener) ||
  481.             iid.equals(Ci.nsISupportsWeakReference) ||
  482.             iid.equals(Ci.nsISupports))
  483.         {
  484.             return this;
  485.         }
  486.  
  487.         throw Components.results.NS_NOINTERFACE;
  488.     },
  489.  
  490.     getContext: function()
  491.     {
  492.         try {
  493.             return TabWatcher.getContextByWindow(this.window);
  494.         } catch (e) {}
  495.         return null;
  496.     }
  497. }
  498.  
  499. // ************************************************************************************************
  500. // Helpers
  501.  
  502. function safeGetName(request)
  503. {
  504.     try {
  505.         return request.name;
  506.     }
  507.     catch (exc) {
  508.     }
  509.  
  510.     return null;
  511. }
  512.  
  513. // ************************************************************************************************
  514. // Registration
  515.  
  516. Firebug.registerModule(Firebug.TabCacheModel);
  517.  
  518. // ************************************************************************************************
  519.  
  520. }});
  521.